<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
	<head>
		<title>log4javascript</title>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<!-- Make IE8 behave like IE7, having gone to all the trouble of making IE work -->
		<meta http-equiv="X-UA-Compatible" content="IE=7" />
		<script type="text/javascript">var isIe = false, isIePre7 = false;</script>
		<!--[if IE]><script type="text/javascript">isIe = true</script><![endif]-->
		<!--[if lt IE 7]><script type="text/javascript">isIePre7 = true</script><![endif]-->
		<script type="text/javascript">
			//<![CDATA[
			var loggingEnabled = true;
			var logQueuedEventsTimer = null;
			var logEntries = [];
			var logEntriesAndSeparators = [];
			var logItems = [];
			var renderDelay = 100;
			var unrenderedLogItemsExist = false;
			var rootGroup, currentGroup = null;
			var loaded = false;
			var currentLogItem = null;
			var logMainContainer;

			function copyProperties(obj, props) {
				for (var i in props) {
					obj[i] = props[i];
				}
			}

			/*----------------------------------------------------------------*/

			function LogItem() {
			}

			LogItem.prototype = {
				mainContainer: null,
				wrappedContainer: null,
				unwrappedContainer: null,
				group: null,

				appendToLog: function() {
					for (var i = 0, len = this.elementContainers.length; i < len; i++) {
						this.elementContainers[i].appendToLog();
					}
					this.group.update();
				},

				doRemove: function(doUpdate, removeFromGroup) {
					if (this.rendered) {
						for (var i = 0, len = this.elementContainers.length; i < len; i++) {
							this.elementContainers[i].remove();
						}
						this.unwrappedElementContainer = null;
						this.wrappedElementContainer = null;
						this.mainElementContainer = null;
					}
					if (this.group && removeFromGroup) {
						this.group.removeChild(this, doUpdate);
					}
					if (this === currentLogItem) {
						currentLogItem = null;
					}
				},

				remove: function(doUpdate, removeFromGroup) {
					this.doRemove(doUpdate, removeFromGroup);
				},

				render: function() {},

				accept: function(visitor) {
					visitor.visit(this);
				},

				getUnwrappedDomContainer: function() {
					return this.group.unwrappedElementContainer.contentDiv;
				},

				getWrappedDomContainer: function() {
					return this.group.wrappedElementContainer.contentDiv;
				},

				getMainDomContainer: function() {
					return this.group.mainElementContainer.contentDiv;
				}
			};

			LogItem.serializedItemKeys = {LOG_ENTRY: 0, GROUP_START: 1, GROUP_END: 2};

			/*----------------------------------------------------------------*/

			function LogItemContainerElement() {
			}

			LogItemContainerElement.prototype = {
				appendToLog: function() {
					var insertBeforeFirst = (newestAtTop && this.containerDomNode.hasChildNodes());
					if (insertBeforeFirst) {
						this.containerDomNode.insertBefore(this.mainDiv, this.containerDomNode.firstChild);
					} else {
						this.containerDomNode.appendChild(this.mainDiv);
					}
				}
			};

			/*----------------------------------------------------------------*/

			function SeparatorElementContainer(containerDomNode) {
				this.containerDomNode = containerDomNode;
				this.mainDiv = document.createElement("div");
				this.mainDiv.className = "separator";
				this.mainDiv.innerHTML = "&nbsp;";
			}

			SeparatorElementContainer.prototype = new LogItemContainerElement();

			SeparatorElementContainer.prototype.remove = function() {
				this.mainDiv.parentNode.removeChild(this.mainDiv);
				this.mainDiv = null;
			};

			/*----------------------------------------------------------------*/

			function Separator() {
				this.rendered = false;
			}

			Separator.prototype = new LogItem();

			copyProperties(Separator.prototype, {
				render: function() {
					var containerDomNode = this.group.contentDiv;
					if (isIe) {
						this.unwrappedElementContainer = new SeparatorElementContainer(this.getUnwrappedDomContainer());
						this.wrappedElementContainer = new SeparatorElementContainer(this.getWrappedDomContainer());
						this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];
					} else {
						this.mainElementContainer = new SeparatorElementContainer(this.getMainDomContainer());
						this.elementContainers = [this.mainElementContainer];
					}
					this.content = this.formattedMessage;
					this.rendered = true;
				}
			});

			/*----------------------------------------------------------------*/

			function GroupElementContainer(group, containerDomNode, isRoot, isWrapped) {
				this.group = group;
				this.containerDomNode = containerDomNode;
				this.isRoot = isRoot;
				this.isWrapped = isWrapped;
				this.expandable = false;

				if (this.isRoot) {
					if (isIe) {
						this.contentDiv = logMainContainer.appendChild(document.createElement("div"));
						this.contentDiv.id = this.isWrapped ? "log_wrapped" : "log_unwrapped";
					} else {
						this.contentDiv = logMainContainer;
					}
				} else {
					var groupElementContainer = this;
					
					this.mainDiv = document.createElement("div");
					this.mainDiv.className = "group";

					this.headingDiv = this.mainDiv.appendChild(document.createElement("div"));
					this.headingDiv.className = "groupheading";

					this.expander = this.headingDiv.appendChild(document.createElement("span"));
					this.expander.className = "expander unselectable greyedout";
					this.expander.unselectable = true;
					var expanderText = this.group.expanded ? "-" : "+";
					this.expanderTextNode = this.expander.appendChild(document.createTextNode(expanderText));
					
					this.headingDiv.appendChild(document.createTextNode(" " + this.group.name));

					this.contentDiv = this.mainDiv.appendChild(document.createElement("div"));
					var contentCssClass = this.group.expanded ? "expanded" : "collapsed";
					this.contentDiv.className = "groupcontent " + contentCssClass;

					this.expander.onclick = function() {
						if (groupElementContainer.group.expandable) {
							groupElementContainer.group.toggleExpanded();
						}
					};
				}
			}

			GroupElementContainer.prototype = new LogItemContainerElement();

			copyProperties(GroupElementContainer.prototype, {
				toggleExpanded: function() {
					if (!this.isRoot) {
						var oldCssClass, newCssClass, expanderText;
						if (this.group.expanded) {
							newCssClass = "expanded";
							oldCssClass = "collapsed";
							expanderText = "-";
						} else {
							newCssClass = "collapsed";
							oldCssClass = "expanded";
							expanderText = "+";
						}
						replaceClass(this.contentDiv, newCssClass, oldCssClass);
						this.expanderTextNode.nodeValue = expanderText;
					}
				},

				remove: function() {
					if (!this.isRoot) {
						this.headingDiv = null;
						this.expander.onclick = null;
						this.expander = null;
						this.expanderTextNode = null;
						this.contentDiv = null;
						this.containerDomNode = null;
						this.mainDiv.parentNode.removeChild(this.mainDiv);
						this.mainDiv = null;
					}
				},

				reverseChildren: function() {
					// Invert the order of the log entries
					var node = null;

					// Remove all the log container nodes
					var childDomNodes = [];
					while ((node = this.contentDiv.firstChild)) {
						this.contentDiv.removeChild(node);
						childDomNodes.push(node);
					}

					// Put them all back in reverse order
					while ((node = childDomNodes.pop())) {
						this.contentDiv.appendChild(node);
					}
				},

				update: function() {
					if (!this.isRoot) {
						if (this.group.expandable) {
							removeClass(this.expander, "greyedout");
						} else {
							addClass(this.expander, "greyedout");
						}
					}
				},

				clear: function() {
					if (this.isRoot) {
						this.contentDiv.innerHTML = "";
					}
				}
			});

			/*----------------------------------------------------------------*/

			function Group(name, isRoot, initiallyExpanded) {
				this.name = name;
				this.group = null;
				this.isRoot = isRoot;
				this.initiallyExpanded = initiallyExpanded;
				this.elementContainers = [];
				this.children = [];
				this.expanded = initiallyExpanded;
				this.rendered = false;
				this.expandable = false;
			}

			Group.prototype = new LogItem();

			copyProperties(Group.prototype, {
				addChild: function(logItem) {
					this.children.push(logItem);
					logItem.group = this;
				},

				render: function() {
					if (isIe) {
						var unwrappedDomContainer, wrappedDomContainer;
						if (this.isRoot) {
							unwrappedDomContainer = logMainContainer;
							wrappedDomContainer = logMainContainer;
						} else {
							unwrappedDomContainer = this.getUnwrappedDomContainer();
							wrappedDomContainer = this.getWrappedDomContainer();
						}
						this.unwrappedElementContainer = new GroupElementContainer(this, unwrappedDomContainer, this.isRoot, false);
						this.wrappedElementContainer = new GroupElementContainer(this, wrappedDomContainer, this.isRoot, true);
						this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];
					} else {
						var mainDomContainer = this.isRoot ? logMainContainer : this.getMainDomContainer();
						this.mainElementContainer = new GroupElementContainer(this, mainDomContainer, this.isRoot, false);
						this.elementContainers = [this.mainElementContainer];
					}
					this.rendered = true;
				},

				toggleExpanded: function() {
					this.expanded = !this.expanded;
					for (var i = 0, len = this.elementContainers.length; i < len; i++) {
						this.elementContainers[i].toggleExpanded();
					}
				},

				expand: function() {
					if (!this.expanded) {
						this.toggleExpanded();
					}
				},

				accept: function(visitor) {
					visitor.visitGroup(this);
				},

				reverseChildren: function() {
					if (this.rendered) {
						for (var i = 0, len = this.elementContainers.length; i < len; i++) {
							this.elementContainers[i].reverseChildren();
						}
					}
				},

				update: function() {
					var previouslyExpandable = this.expandable;
					this.expandable = (this.children.length !== 0);
					if (this.expandable !== previouslyExpandable) {
						for (var i = 0, len = this.elementContainers.length; i < len; i++) {
							this.elementContainers[i].update();
						}
					}
				},

				flatten: function() {
					var visitor = new GroupFlattener();
					this.accept(visitor);
					return visitor.logEntriesAndSeparators;
				},

				removeChild: function(child, doUpdate) {
					array_remove(this.children, child);
					child.group = null;
					if (doUpdate) {
						this.update();
					}
				},

				remove: function(doUpdate, removeFromGroup) {
					for (var i = 0, len = this.children.length; i < len; i++) {
						this.children[i].remove(false, false);
					}
					this.children = [];
					this.update();
					if (this === currentGroup) {
						currentGroup = this.group;
					}
					this.doRemove(doUpdate, removeFromGroup);
				},

				serialize: function(items) {
					items.push([LogItem.serializedItemKeys.GROUP_START, this.name]);
					for (var i = 0, len = this.children.length; i < len; i++) {
						this.children[i].serialize(items);
					}
					if (this !== currentGroup) {
						items.push([LogItem.serializedItemKeys.GROUP_END]);
					}
				},

				clear: function() {
					for (var i = 0, len = this.elementContainers.length; i < len; i++) {
						this.elementContainers[i].clear();
					}
				}
			});

			/*----------------------------------------------------------------*/

			function LogEntryElementContainer() {
			}

			LogEntryElementContainer.prototype = new LogItemContainerElement();

			copyProperties(LogEntryElementContainer.prototype, {
				remove: function() {
					this.doRemove();
				},

				doRemove: function() {
					this.mainDiv.parentNode.removeChild(this.mainDiv);
					this.mainDiv = null;
					this.contentElement = null;
					this.containerDomNode = null;
				},

				setContent: function(content, wrappedContent) {
					if (content === this.formattedMessage) {
						this.contentElement.innerHTML = "";
						this.contentElement.appendChild(document.createTextNode(this.formattedMessage));
					} else {
						this.contentElement.innerHTML = content;
					}
				},

				setSearchMatch: function(isMatch) {
					var oldCssClass = isMatch ? "searchnonmatch" : "searchmatch";
					var newCssClass = isMatch ? "searchmatch" : "searchnonmatch";
					replaceClass(this.mainDiv, newCssClass, oldCssClass);
				},

				clearSearch: function() {
					removeClass(this.mainDiv, "searchmatch");
					removeClass(this.mainDiv, "searchnonmatch");
				}
			});

			/*----------------------------------------------------------------*/

			function LogEntryWrappedElementContainer(logEntry, containerDomNode) {
				this.logEntry = logEntry;
				this.containerDomNode = containerDomNode;
				this.mainDiv = document.createElement("div");
				this.mainDiv.appendChild(document.createTextNode(this.logEntry.formattedMessage));
				this.mainDiv.className = "logentry wrapped " + this.logEntry.level;
				this.contentElement = this.mainDiv;
			}

			LogEntryWrappedElementContainer.prototype = new LogEntryElementContainer();

			LogEntryWrappedElementContainer.prototype.setContent = function(content, wrappedContent) {
				if (content === this.formattedMessage) {
					this.contentElement.innerHTML = "";
					this.contentElement.appendChild(document.createTextNode(this.formattedMessage));
				} else {
					this.contentElement.innerHTML = wrappedContent;
				}
			};

			/*----------------------------------------------------------------*/

			function LogEntryUnwrappedElementContainer(logEntry, containerDomNode) {
				this.logEntry = logEntry;
				this.containerDomNode = containerDomNode;
				this.mainDiv = document.createElement("div");
				this.mainDiv.className = "logentry unwrapped " + this.logEntry.level;
				this.pre = this.mainDiv.appendChild(document.createElement("pre"));
				this.pre.appendChild(document.createTextNode(this.logEntry.formattedMessage));
				this.pre.className = "unwrapped";
				this.contentElement = this.pre;
			}

			LogEntryUnwrappedElementContainer.prototype = new LogEntryElementContainer();

			LogEntryUnwrappedElementContainer.prototype.remove = function() {
				this.doRemove();
				this.pre = null;
			};

			/*----------------------------------------------------------------*/

			function LogEntryMainElementContainer(logEntry, containerDomNode) {
				this.logEntry = logEntry;
				this.containerDomNode = containerDomNode;
				this.mainDiv = document.createElement("div");
				this.mainDiv.className = "logentry nonielogentry " + this.logEntry.level;
				this.contentElement = this.mainDiv.appendChild(document.createElement("span"));
				this.contentElement.appendChild(document.createTextNode(this.logEntry.formattedMessage));
			}

			LogEntryMainElementContainer.prototype = new LogEntryElementContainer();

			/*----------------------------------------------------------------*/

			function LogEntry(level, formattedMessage) {
				this.level = level;
				this.formattedMessage = formattedMessage;
				this.rendered = false;
			}

			LogEntry.prototype = new LogItem();

			copyProperties(LogEntry.prototype, {
				render: function() {
					var logEntry = this;
					var containerDomNode = this.group.contentDiv;

					// Support for the CSS attribute white-space in IE for Windows is
					// non-existent pre version 6 and slightly odd in 6, so instead
					// use two different HTML elements
					if (isIe) {
						this.formattedMessage = this.formattedMessage.replace(/\r\n/g, "\r"); // Workaround for IE's treatment of white space
						this.unwrappedElementContainer = new LogEntryUnwrappedElementContainer(this, this.getUnwrappedDomContainer());
						this.wrappedElementContainer = new LogEntryWrappedElementContainer(this, this.getWrappedDomContainer());
						this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];
					} else {
						this.mainElementContainer = new LogEntryMainElementContainer(this, this.getMainDomContainer());
						this.elementContainers = [this.mainElementContainer];
					}
					this.content = this.formattedMessage;
					this.rendered = true;
				},

				setContent: function(content, wrappedContent) {
					if (content != this.content) {
						if (isIe && (content !== this.formattedMessage)) {
							content = content.replace(/\r\n/g, "\r"); // Workaround for IE's treatment of white space
						}
						for (var i = 0, len = this.elementContainers.length; i < len; i++) {
							this.elementContainers[i].setContent(content, wrappedContent);
						}
						this.content = content;
					}
				},

				getSearchMatches: function() {
					var matches = [];
					var i, len;
					if (isIe) {
						var unwrappedEls = getElementsByClass(this.unwrappedElementContainer.mainDiv, "searchterm", "span");
						var wrappedEls = getElementsByClass(this.wrappedElementContainer.mainDiv, "searchterm", "span");
						for (i = 0, len = unwrappedEls.length; i < len; i++) {
							matches[i] = new Match(this.level, null, unwrappedEls[i], wrappedEls[i]);
						}
					} else {
						var els = getElementsByClass(this.mainElementContainer.mainDiv, "searchterm", "span");
						for (i = 0, len = els.length; i < len; i++) {
							matches[i] = new Match(this.level, els[i]);
						}
					}
					return matches;
				},

				setSearchMatch: function(isMatch) {
					for (var i = 0, len = this.elementContainers.length; i < len; i++) {
						this.elementContainers[i].setSearchMatch(isMatch);
					}
				},

				clearSearch: function() {
					for (var i = 0, len = this.elementContainers.length; i < len; i++) {
						this.elementContainers[i].clearSearch();
					}
				},

				accept: function(visitor) {
					visitor.visitLogEntry(this);
				},

				serialize: function(items) {
					items.push([LogItem.serializedItemKeys.LOG_ENTRY, this.level, this.formattedMessage]);
				}
			});

			/*----------------------------------------------------------------*/

			function LogItemVisitor() {
			}

			LogItemVisitor.prototype = {
				visit: function(logItem) {
				},

				visitParent: function(logItem) {
					if (logItem.group) {
						logItem.group.accept(this);
					}
				},

				visitChildren: function(logItem) {
					for (var i = 0, len = logItem.children.length; i < len; i++) {
						logItem.children[i].accept(this);
					}
				},

				visitLogEntry: function(logEntry) {
					this.visit(logEntry);
				},

				visitSeparator: function(separator) {
					this.visit(separator);
				},

				visitGroup: function(group) {
					this.visit(group);
				}
			};

			/*----------------------------------------------------------------*/

			function GroupFlattener() {
				this.logEntriesAndSeparators = [];
			}

			GroupFlattener.prototype = new LogItemVisitor();

			GroupFlattener.prototype.visitGroup = function(group) {
				this.visitChildren(group);
			};

			GroupFlattener.prototype.visitLogEntry = function(logEntry) {
				this.logEntriesAndSeparators.push(logEntry);
			};

			GroupFlattener.prototype.visitSeparator = function(separator) {
				this.logEntriesAndSeparators.push(separator);
			};

			/*----------------------------------------------------------------*/

			window.onload = function() {
				// Sort out document.domain
				if (location.search) {
					var queryBits = unescape(location.search).substr(1).split("&"), nameValueBits;
					for (var i = 0, len = queryBits.length; i < len; i++) {
						nameValueBits = queryBits[i].split("=");
						if (nameValueBits[0] == "log4javascript_domain") {
							document.domain = nameValueBits[1];
							break;
						}
					}
				}

				// Create DOM objects
				logMainContainer = $("log");
				if (isIePre7) {
					addClass(logMainContainer, "oldIe");
				}

				rootGroup = new Group("root", true);
				rootGroup.render();
				currentGroup = rootGroup;
				
				setCommandInputWidth();
				setLogContainerHeight();
				toggleLoggingEnabled();
				toggleSearchEnabled();
				toggleSearchFilter();
				toggleSearchHighlight();
				applyFilters();
				checkAllLevels();
				toggleWrap();
				toggleNewestAtTop();
				toggleScrollToLatest();
				renderQueuedLogItems();
				loaded = true;
				$("command").value = "";
				$("command").autocomplete = "off";
				$("command").onkeydown = function(evt) {
					evt = getEvent(evt);
					if (evt.keyCode == 10 || evt.keyCode == 13) { // Return/Enter
						evalCommandLine();
						stopPropagation(evt);
					} else if (evt.keyCode == 27) { // Escape
						this.value = "";
						this.focus();
					} else if (evt.keyCode == 38 && commandHistory.length > 0) { // Up
						currentCommandIndex = Math.max(0, currentCommandIndex - 1);
						this.value = commandHistory[currentCommandIndex];
						moveCaretToEnd(this);
					} else if (evt.keyCode == 40 && commandHistory.length > 0) { // Down
						currentCommandIndex = Math.min(commandHistory.length - 1, currentCommandIndex + 1);
						this.value = commandHistory[currentCommandIndex];
						moveCaretToEnd(this);
					}
				};

				// Prevent the keypress moving the caret in Firefox
				$("command").onkeypress = function(evt) {
					evt = getEvent(evt);
					if (evt.keyCode == 38 && commandHistory.length > 0 && evt.preventDefault) { // Up
						evt.preventDefault();
					}
				};

				// Prevent the keyup event blurring the input in Opera
				$("command").onkeyup = function(evt) {
					evt = getEvent(evt);
					if (evt.keyCode == 27 && evt.preventDefault) { // Up
						evt.preventDefault();
						this.focus();
					}
				};

				// Add document keyboard shortcuts
				document.onkeydown = function keyEventHandler(evt) {
					evt = getEvent(evt);
					switch (evt.keyCode) {
						case 69: // Ctrl + shift + E: re-execute last command
							if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {
								evalLastCommand();
								cancelKeyEvent(evt);
								return false;
							}
							break;
						case 75: // Ctrl + shift + K: focus search
							if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {
								focusSearch();
								cancelKeyEvent(evt);
								return false;
							}
							break;
						case 40: // Ctrl + shift + down arrow: focus command line
						case 76: // Ctrl + shift + L: focus command line
							if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {
								focusCommandLine();
								cancelKeyEvent(evt);
								return false;
							}
							break;
					}
				};

				// Workaround to make sure log div starts at the correct size
				setTimeout(setLogContainerHeight, 20);

				setShowCommandLine(showCommandLine);
				doSearch();
			};

			window.onunload = function() {
				if (mainWindowExists()) {
					appender.unload();
				}
				appender = null;
			};

			/*----------------------------------------------------------------*/

			function toggleLoggingEnabled() {
				setLoggingEnabled($("enableLogging").checked);
			}

			function setLoggingEnabled(enable) {
				loggingEnabled = enable;
			}

			var appender = null;

			function setAppender(appenderParam) {
				appender = appenderParam;
			}

			function setShowCloseButton(showCloseButton) {
				$("closeButton").style.display = showCloseButton ? "inline" : "none";
			}

			function setShowHideButton(showHideButton) {
				$("hideButton").style.display = showHideButton ? "inline" : "none";
			}

			var newestAtTop = false;

			/*----------------------------------------------------------------*/

			function LogItemContentReverser() {
			}
			
			LogItemContentReverser.prototype = new LogItemVisitor();
			
			LogItemContentReverser.prototype.visitGroup = function(group) {
				group.reverseChildren();
				this.visitChildren(group);
			};

			/*----------------------------------------------------------------*/

			function setNewestAtTop(isNewestAtTop) {
				var oldNewestAtTop = newestAtTop;
				var i, iLen, j, jLen;
				newestAtTop = Boolean(isNewestAtTop);
				if (oldNewestAtTop != newestAtTop) {
					var visitor = new LogItemContentReverser();
					rootGroup.accept(visitor);

					// Reassemble the matches array
					if (currentSearch) {
						var currentMatch = currentSearch.matches[currentMatchIndex];
						var matchIndex = 0;
						var matches = [];
						var actOnLogEntry = function(logEntry) {
							var logEntryMatches = logEntry.getSearchMatches();
							for (j = 0, jLen = logEntryMatches.length; j < jLen; j++) {
								matches[matchIndex] = logEntryMatches[j];
								if (currentMatch && logEntryMatches[j].equals(currentMatch)) {
									currentMatchIndex = matchIndex;
								}
								matchIndex++;
							}
						};
						if (newestAtTop) {
							for (i = logEntries.length - 1; i >= 0; i--) {
								actOnLogEntry(logEntries[i]);
							}
						} else {
							for (i = 0, iLen = logEntries.length; i < iLen; i++) {
								actOnLogEntry(logEntries[i]);
							}
						}
						currentSearch.matches = matches;
						if (currentMatch) {
							currentMatch.setCurrent();
						}
					} else if (scrollToLatest) {
						doScrollToLatest();
					}
				}
				$("newestAtTop").checked = isNewestAtTop;
			}

			function toggleNewestAtTop() {
				var isNewestAtTop = $("newestAtTop").checked;
				setNewestAtTop(isNewestAtTop);
			}

			var scrollToLatest = true;

			function setScrollToLatest(isScrollToLatest) {
				scrollToLatest = isScrollToLatest;
				if (scrollToLatest) {
					doScrollToLatest();
				}
				$("scrollToLatest").checked = isScrollToLatest;
			}

			function toggleScrollToLatest() {
				var isScrollToLatest = $("scrollToLatest").checked;
				setScrollToLatest(isScrollToLatest);
			}

			function doScrollToLatest() {
				var l = logMainContainer;
				if (typeof l.scrollTop != "undefined") {
					if (newestAtTop) {
						l.scrollTop = 0;
					} else {
						var latestLogEntry = l.lastChild;
						if (latestLogEntry) {
							l.scrollTop = l.scrollHeight;
						}
					}
				}
			}

			var closeIfOpenerCloses = true;

			function setCloseIfOpenerCloses(isCloseIfOpenerCloses) {
				closeIfOpenerCloses = isCloseIfOpenerCloses;
			}

			var maxMessages = null;

			function setMaxMessages(max) {
				maxMessages = max;
				pruneLogEntries();
			}

			var showCommandLine = false;

			function setShowCommandLine(isShowCommandLine) {
				showCommandLine = isShowCommandLine;
				if (loaded) {
					$("commandLine").style.display = showCommandLine ? "block" : "none";
					setCommandInputWidth();
					setLogContainerHeight();
				}
			}

			function focusCommandLine() {
				if (loaded) {
					$("command").focus();
				}
			}

			function focusSearch() {
				if (loaded) {
					$("searchBox").focus();
				}
			}

			function getLogItems() {
				var items = [];
				for (var i = 0, len = logItems.length; i < len; i++) {
					logItems[i].serialize(items);
				}
				return items;
			}

			function setLogItems(items) {
				var loggingReallyEnabled = loggingEnabled;
				// Temporarily turn logging on
				loggingEnabled = true;
				for (var i = 0, len = items.length; i < len; i++) {
					switch (items[i][0]) {
						case LogItem.serializedItemKeys.LOG_ENTRY:
							log(items[i][1], items[i][2]);
							break;
						case LogItem.serializedItemKeys.GROUP_START:
							group(items[i][1]);
							break;
						case LogItem.serializedItemKeys.GROUP_END:
							groupEnd();
							break;
					}
				}
				loggingEnabled = loggingReallyEnabled;
			}

			function log(logLevel, formattedMessage) {
				if (loggingEnabled) {
					var logEntry = new LogEntry(logLevel, formattedMessage);
					logEntries.push(logEntry);
					logEntriesAndSeparators.push(logEntry);
					logItems.push(logEntry);
					currentGroup.addChild(logEntry);
					if (loaded) {
						if (logQueuedEventsTimer !== null) {
							clearTimeout(logQueuedEventsTimer);
						}
						logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay);
						unrenderedLogItemsExist = true;
					}
				}
			}

			function renderQueuedLogItems() {
				logQueuedEventsTimer = null;
				var pruned = pruneLogEntries();

				// Render any unrendered log entries and apply the current search to them
				var initiallyHasMatches = currentSearch ? currentSearch.hasMatches() : false;
				for (var i = 0, len = logItems.length; i < len; i++) {
					if (!logItems[i].rendered) {
						logItems[i].render();
						logItems[i].appendToLog();
						if (currentSearch && (logItems[i] instanceof LogEntry)) {
							currentSearch.applyTo(logItems[i]);
						}
					}
				}
				if (currentSearch) {
					if (pruned) {
						if (currentSearch.hasVisibleMatches()) {
							if (currentMatchIndex === null) {
								setCurrentMatchIndex(0);
							}
							displayMatches();
						} else {
							displayNoMatches();
						}
					} else if (!initiallyHasMatches && currentSearch.hasVisibleMatches()) {
						setCurrentMatchIndex(0);
						displayMatches();
					}
				}
				if (scrollToLatest) {
					doScrollToLatest();
				}
				unrenderedLogItemsExist = false;
			}

			function pruneLogEntries() {
				if ((maxMessages !== null) && (logEntriesAndSeparators.length > maxMessages)) {
					var numberToDelete = logEntriesAndSeparators.length - maxMessages;
					var prunedLogEntries = logEntriesAndSeparators.slice(0, numberToDelete);
					if (currentSearch) {
						currentSearch.removeMatches(prunedLogEntries);
					}
					var group;
					for (var i = 0; i < numberToDelete; i++) {
						group = logEntriesAndSeparators[i].group;
						array_remove(logItems, logEntriesAndSeparators[i]);
						array_remove(logEntries, logEntriesAndSeparators[i]);
						logEntriesAndSeparators[i].remove(true, true);
						if (group.children.length === 0 && group !== currentGroup && group !== rootGroup) {
							array_remove(logItems, group);
							group.remove(true, true);
						}
					}
					logEntriesAndSeparators = array_removeFromStart(logEntriesAndSeparators, numberToDelete);
					return true;
				}
				return false;
			}

			function group(name, startExpanded) {
				if (loggingEnabled) {
					initiallyExpanded = (typeof startExpanded === "undefined") ? true : Boolean(startExpanded);
					var newGroup = new Group(name, false, initiallyExpanded);
					currentGroup.addChild(newGroup);
					currentGroup = newGroup;
					logItems.push(newGroup);
					if (loaded) {
						if (logQueuedEventsTimer !== null) {
							clearTimeout(logQueuedEventsTimer);
						}
						logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay);
						unrenderedLogItemsExist = true;
					}
				}
			}

			function groupEnd() {
				currentGroup = (currentGroup === rootGroup) ? rootGroup : currentGroup.group;
			}

			function mainPageReloaded() {
				currentGroup = rootGroup;
				var separator = new Separator();
				logEntriesAndSeparators.push(separator);
				logItems.push(separator);
				currentGroup.addChild(separator);
			}

			function closeWindow() {
				if (appender && mainWindowExists()) {
					appender.close(true);
				} else {
					window.close();
				}
			}

			function hide() {
				if (appender && mainWindowExists()) {
					appender.hide();
				}
			}

			var mainWindow = window;
			var windowId = "log4javascriptConsoleWindow_" + new Date().getTime() + "_" + ("" + Math.random()).substr(2);

			function setMainWindow(win) {
				mainWindow = win;
				mainWindow[windowId] = window;
				// If this is a pop-up, poll the opener to see if it's closed
				if (opener && closeIfOpenerCloses) {
					pollOpener();
				}
			}

			function pollOpener() {
				if (closeIfOpenerCloses) {
					if (mainWindowExists()) {
						setTimeout(pollOpener, 500);
					} else {
						closeWindow();
					}
				}
			}

			function mainWindowExists() {
				try {
					return (mainWindow && !mainWindow.closed &&
						mainWindow[windowId] == window);
				} catch (ex) {}
				return false;
			}

			var logLevels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"];

			function getCheckBox(logLevel) {
				return $("switch_" + logLevel);
			}

			function getIeWrappedLogContainer() {
				return $("log_wrapped");
			}

			function getIeUnwrappedLogContainer() {
				return $("log_unwrapped");
			}

			function applyFilters() {
				for (var i = 0; i < logLevels.length; i++) {
					if (getCheckBox(logLevels[i]).checked) {
						addClass(logMainContainer, logLevels[i]);
					} else {
						removeClass(logMainContainer, logLevels[i]);
					}
				}
				updateSearchFromFilters();
			}

			function toggleAllLevels() {
				var turnOn = $("switch_ALL").checked;
				for (var i = 0; i < logLevels.length; i++) {
					getCheckBox(logLevels[i]).checked = turnOn;
					if (turnOn) {
						addClass(logMainContainer, logLevels[i]);
					} else {
						removeClass(logMainContainer, logLevels[i]);
					}
				}
			}

			function checkAllLevels() {
				for (var i = 0; i < logLevels.length; i++) {
					if (!getCheckBox(logLevels[i]).checked) {
						getCheckBox("ALL").checked = false;
						return;
					}
				}
				getCheckBox("ALL").checked = true;
			}

			function clearLog() {
				rootGroup.clear();
				currentGroup = rootGroup;
				logEntries = [];
				logItems = [];
				logEntriesAndSeparators = [];
 				doSearch();
			}

			function toggleWrap() {
				var enable = $("wrap").checked;
				if (enable) {
					addClass(logMainContainer, "wrap");
				} else {
					removeClass(logMainContainer, "wrap");
				}
				refreshCurrentMatch();
			}

			/* ------------------------------------------------------------------- */

			// Search

			var searchTimer = null;

			function scheduleSearch() {
				try {
					clearTimeout(searchTimer);
				} catch (ex) {
					// Do nothing
				}
				searchTimer = setTimeout(doSearch, 500);
			}

			function Search(searchTerm, isRegex, searchRegex, isCaseSensitive) {
				this.searchTerm = searchTerm;
				this.isRegex = isRegex;
				this.searchRegex = searchRegex;
				this.isCaseSensitive = isCaseSensitive;
				this.matches = [];
			}

			Search.prototype = {
				hasMatches: function() {
					return this.matches.length > 0;
				},

				hasVisibleMatches: function() {
					if (this.hasMatches()) {
						for (var i = 0; i < this.matches.length; i++) {
							if (this.matches[i].isVisible()) {
								return true;
							}
						}
					}
					return false;
				},

				match: function(logEntry) {
					var entryText = String(logEntry.formattedMessage);
					var matchesSearch = false;
					if (this.isRegex) {
						matchesSearch = this.searchRegex.test(entryText);
					} else if (this.isCaseSensitive) {
						matchesSearch = (entryText.indexOf(this.searchTerm) > -1);
					} else {
						matchesSearch = (entryText.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1);
					}
					return matchesSearch;
				},

				getNextVisibleMatchIndex: function() {
					for (var i = currentMatchIndex + 1; i < this.matches.length; i++) {
						if (this.matches[i].isVisible()) {
							return i;
						}
					}
					// Start again from the first match
					for (i = 0; i <= currentMatchIndex; i++) {
						if (this.matches[i].isVisible()) {
							return i;
						}
					}
					return -1;
				},

				getPreviousVisibleMatchIndex: function() {
					for (var i = currentMatchIndex - 1; i >= 0; i--) {
						if (this.matches[i].isVisible()) {
							return i;
						}
					}
					// Start again from the last match
					for (var i = this.matches.length - 1; i >= currentMatchIndex; i--) {
						if (this.matches[i].isVisible()) {
							return i;
						}
					}
					return -1;
				},

				applyTo: function(logEntry) {
					var doesMatch = this.match(logEntry);
					if (doesMatch) {
						logEntry.group.expand();
						logEntry.setSearchMatch(true);
						var logEntryContent;
						var wrappedLogEntryContent;
						var searchTermReplacementStartTag = "<span class=\"searchterm\">";
						var searchTermReplacementEndTag = "<" + "/span>";
						var preTagName = isIe ? "pre" : "span";
						var preStartTag = "<" + preTagName + " class=\"pre\">";
						var preEndTag = "<" + "/" + preTagName + ">";
						var startIndex = 0;
						var searchIndex, matchedText, textBeforeMatch;
						if (this.isRegex) {
							var flags = this.isCaseSensitive ? "g" : "gi";
							var capturingRegex = new RegExp("(" + this.searchRegex.source + ")", flags);

							// Replace the search term with temporary tokens for the start and end tags
							var rnd = ("" + Math.random()).substr(2);
							var startToken = "%%s" + rnd + "%%";
							var endToken = "%%e" + rnd + "%%";
							logEntryContent = logEntry.formattedMessage.replace(capturingRegex, startToken + "$1" + endToken);

							// Escape the HTML to get rid of angle brackets
							logEntryContent = escapeHtml(logEntryContent);

							// Substitute the proper HTML back in for the search match
							var result;
							var searchString = logEntryContent;
							logEntryContent = "";
							wrappedLogEntryContent = "";
							while ((searchIndex = searchString.indexOf(startToken, startIndex)) > -1) {
								var endTokenIndex = searchString.indexOf(endToken, searchIndex);
								matchedText = searchString.substring(searchIndex + startToken.length, endTokenIndex);
								textBeforeMatch = searchString.substring(startIndex, searchIndex);
								logEntryContent += preStartTag + textBeforeMatch + preEndTag;
								logEntryContent += searchTermReplacementStartTag + preStartTag + matchedText +
									preEndTag + searchTermReplacementEndTag;
								if (isIe) {
									wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag +
										matchedText + searchTermReplacementEndTag;
								}
								startIndex = endTokenIndex + endToken.length;
							}
							logEntryContent += preStartTag + searchString.substr(startIndex) + preEndTag;
							if (isIe) {
								wrappedLogEntryContent += searchString.substr(startIndex);
							}
						} else {
							logEntryContent = "";
							wrappedLogEntryContent = "";
							var searchTermReplacementLength = searchTermReplacementStartTag.length +
								this.searchTerm.length + searchTermReplacementEndTag.length;
							var searchTermLength = this.searchTerm.length;
							var searchTermLowerCase = this.searchTerm.toLowerCase();
							var logTextLowerCase = logEntry.formattedMessage.toLowerCase();
							while ((searchIndex = logTextLowerCase.indexOf(searchTermLowerCase, startIndex)) > -1) {
								matchedText = escapeHtml(logEntry.formattedMessage.substr(searchIndex, this.searchTerm.length));
								textBeforeMatch = escapeHtml(logEntry.formattedMessage.substring(startIndex, searchIndex));
								var searchTermReplacement = searchTermReplacementStartTag +
									preStartTag + matchedText + preEndTag + searchTermReplacementEndTag;
								logEntryContent += preStartTag + textBeforeMatch + preEndTag + searchTermReplacement;
								if (isIe) {
									wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag +
										matchedText + searchTermReplacementEndTag;
								}
								startIndex = searchIndex + searchTermLength;
							}
							var textAfterLastMatch = escapeHtml(logEntry.formattedMessage.substr(startIndex));
							logEntryContent += preStartTag + textAfterLastMatch + preEndTag;
							if (isIe) {
								wrappedLogEntryContent += textAfterLastMatch;
							}
						}
						logEntry.setContent(logEntryContent, wrappedLogEntryContent);
						var logEntryMatches = logEntry.getSearchMatches();
						this.matches = this.matches.concat(logEntryMatches);
					} else {
						logEntry.setSearchMatch(false);
						logEntry.setContent(logEntry.formattedMessage, logEntry.formattedMessage);
					}
					return doesMatch;
				},

				removeMatches: function(logEntries) {
					var matchesToRemoveCount = 0;
					var currentMatchRemoved = false;
					var matchesToRemove = [];
					var i, iLen, j, jLen;

					// Establish the list of matches to be removed
					for (i = 0, iLen = this.matches.length; i < iLen; i++) {
						for (j = 0, jLen = logEntries.length; j < jLen; j++) {
							if (this.matches[i].belongsTo(logEntries[j])) {
								matchesToRemove.push(this.matches[i]);
								if (i === currentMatchIndex) {
									currentMatchRemoved = true;
								}
							}
						}
					}

					// Set the new current match index if the current match has been deleted
					// This will be the first match that appears after the first log entry being
					// deleted, if one exists; otherwise, it's the first match overall
					var newMatch = currentMatchRemoved ? null : this.matches[currentMatchIndex];
					if (currentMatchRemoved) {
						for (i = currentMatchIndex, iLen = this.matches.length; i < iLen; i++) {
							if (this.matches[i].isVisible() && !array_contains(matchesToRemove, this.matches[i])) {
								newMatch = this.matches[i];
								break;
							}
						}
					}

					// Remove the matches
					for (i = 0, iLen = matchesToRemove.length; i < iLen; i++) {
						array_remove(this.matches, matchesToRemove[i]);
						matchesToRemove[i].remove();
					}

					// Set the new match, if one exists
					if (this.hasVisibleMatches()) {
						if (newMatch === null) {
							setCurrentMatchIndex(0);
						} else {
							// Get the index of the new match
							var newMatchIndex = 0;
							for (i = 0, iLen = this.matches.length; i < iLen; i++) {
								if (newMatch === this.matches[i]) {
									newMatchIndex = i;
									break;
								}
							}
							setCurrentMatchIndex(newMatchIndex);
						}
					} else {
						currentMatchIndex = null;
						displayNoMatches();
					}
				}
			};

			function getPageOffsetTop(el, container) {
				var currentEl = el;
				var y = 0;
				while (currentEl && currentEl != container) {
					y += currentEl.offsetTop;
					currentEl = currentEl.offsetParent;
				}
				return y;
			}

			function scrollIntoView(el) {
				var logContainer = logMainContainer;
				// Check if the whole width of the element is visible and centre if not
				if (!$("wrap").checked) {
					var logContainerLeft = logContainer.scrollLeft;
					var logContainerRight = logContainerLeft  + logContainer.offsetWidth;
					var elLeft = el.offsetLeft;
					var elRight = elLeft + el.offsetWidth;
					if (elLeft < logContainerLeft || elRight > logContainerRight) {
						logContainer.scrollLeft = elLeft - (logContainer.offsetWidth - el.offsetWidth) / 2;
					}
				}
				// Check if the whole height of the element is visible and centre if not
				var logContainerTop = logContainer.scrollTop;
				var logContainerBottom = logContainerTop  + logContainer.offsetHeight;
				var elTop = getPageOffsetTop(el) - getToolBarsHeight();
				var elBottom = elTop + el.offsetHeight;
				if (elTop < logContainerTop || elBottom > logContainerBottom) {
					logContainer.scrollTop = elTop - (logContainer.offsetHeight - el.offsetHeight) / 2;
				}
			}

			function Match(logEntryLevel, spanInMainDiv, spanInUnwrappedPre, spanInWrappedDiv) {
				this.logEntryLevel = logEntryLevel;
				this.spanInMainDiv = spanInMainDiv;
				if (isIe) {
					this.spanInUnwrappedPre = spanInUnwrappedPre;
					this.spanInWrappedDiv = spanInWrappedDiv;
				}
				this.mainSpan = isIe ? spanInUnwrappedPre : spanInMainDiv;
			}

			Match.prototype = {
				equals: function(match) {
					return this.mainSpan === match.mainSpan;
				},

				setCurrent: function() {
					if (isIe) {
						addClass(this.spanInUnwrappedPre, "currentmatch");
						addClass(this.spanInWrappedDiv, "currentmatch");
						// Scroll the visible one into view
						var elementToScroll = $("wrap").checked ? this.spanInWrappedDiv : this.spanInUnwrappedPre;
						scrollIntoView(elementToScroll);
					} else {
						addClass(this.spanInMainDiv, "currentmatch");
						scrollIntoView(this.spanInMainDiv);
					}
				},

				belongsTo: function(logEntry) {
					if (isIe) {
						return isDescendant(this.spanInUnwrappedPre, logEntry.unwrappedPre);
					} else {
						return isDescendant(this.spanInMainDiv, logEntry.mainDiv);
					}
				},

				setNotCurrent: function() {
					if (isIe) {
						removeClass(this.spanInUnwrappedPre, "currentmatch");
						removeClass(this.spanInWrappedDiv, "currentmatch");
					} else {
						removeClass(this.spanInMainDiv, "currentmatch");
					}
				},

				isOrphan: function() {
					return isOrphan(this.mainSpan);
				},

				isVisible: function() {
					return getCheckBox(this.logEntryLevel).checked;
				},

				remove: function() {
					if (isIe) {
						this.spanInUnwrappedPre = null;
						this.spanInWrappedDiv = null;
					} else {
						this.spanInMainDiv = null;
					}
				}
			};

			var currentSearch = null;
			var currentMatchIndex = null;

			function doSearch() {
				var searchBox = $("searchBox");
				var searchTerm = searchBox.value;
				var isRegex = $("searchRegex").checked;
				var isCaseSensitive = $("searchCaseSensitive").checked;
				var i;

				if (searchTerm === "") {
					$("searchReset").disabled = true;
					$("searchNav").style.display = "none";
					removeClass(document.body, "searching");
					removeClass(searchBox, "hasmatches");
					removeClass(searchBox, "nomatches");
					for (i = 0; i < logEntries.length; i++) {
						logEntries[i].clearSearch();
						logEntries[i].setContent(logEntries[i].formattedMessage, logEntries[i].formattedMessage);
					}
					currentSearch = null;
					setLogContainerHeight();
				} else {
					$("searchReset").disabled = false;
					$("searchNav").style.display = "block";
					var searchRegex;
					var regexValid;
					if (isRegex) {
						try {
							searchRegex = isCaseSensitive ? new RegExp(searchTerm, "g") : new RegExp(searchTerm, "gi");
							regexValid = true;
							replaceClass(searchBox, "validregex", "invalidregex");
							searchBox.title = "Valid regex";
						} catch (ex) {
							regexValid = false;
							replaceClass(searchBox, "invalidregex", "validregex");
							searchBox.title = "Invalid regex: " + (ex.message ? ex.message : (ex.description ? ex.description : "unknown error"));
							return;
						}
					} else {
						searchBox.title = "";
						removeClass(searchBox, "validregex");
						removeClass(searchBox, "invalidregex");
					}
					addClass(document.body, "searching");
					currentSearch = new Search(searchTerm, isRegex, searchRegex, isCaseSensitive);
					for (i = 0; i < logEntries.length; i++) {
						currentSearch.applyTo(logEntries[i]);
					}
					setLogContainerHeight();

					// Highlight the first search match
					if (currentSearch.hasVisibleMatches()) {
						setCurrentMatchIndex(0);
						displayMatches();
					} else {
						displayNoMatches();
					}
				}
			}

			function updateSearchFromFilters() {
				if (currentSearch) {
					if (currentSearch.hasMatches()) {
						if (currentMatchIndex === null) {
							currentMatchIndex = 0;
						}
						var currentMatch = currentSearch.matches[currentMatchIndex];
						if (currentMatch.isVisible()) {
							displayMatches();
							setCurrentMatchIndex(currentMatchIndex);
						} else {
							currentMatch.setNotCurrent();
							// Find the next visible match, if one exists
							var nextVisibleMatchIndex = currentSearch.getNextVisibleMatchIndex();
							if (nextVisibleMatchIndex > -1) {
								setCurrentMatchIndex(nextVisibleMatchIndex);
								displayMatches();
							} else {
								displayNoMatches();
							}
						}
					} else {
						displayNoMatches();
					}
				}
			}

			function refreshCurrentMatch() {
				if (currentSearch && currentSearch.hasVisibleMatches()) {
					setCurrentMatchIndex(currentMatchIndex);
				}
			}

			function displayMatches() {
				replaceClass($("searchBox"), "hasmatches", "nomatches");
				$("searchBox").title = "" + currentSearch.matches.length + " matches found";
				$("searchNav").style.display = "block";
				setLogContainerHeight();
			}

			function displayNoMatches() {
				replaceClass($("searchBox"), "nomatches", "hasmatches");
				$("searchBox").title = "No matches found";
				$("searchNav").style.display = "none";
				setLogContainerHeight();
			}

			function toggleSearchEnabled(enable) {
				enable = (typeof enable == "undefined") ? !$("searchDisable").checked : enable;
				$("searchBox").disabled = !enable;
				$("searchReset").disabled = !enable;
				$("searchRegex").disabled = !enable;
				$("searchNext").disabled = !enable;
				$("searchPrevious").disabled = !enable;
				$("searchCaseSensitive").disabled = !enable;
				$("searchNav").style.display = (enable && ($("searchBox").value !== "") &&
						currentSearch && currentSearch.hasVisibleMatches()) ?
					"block" : "none";
				if (enable) {
					removeClass($("search"), "greyedout");
					addClass(document.body, "searching");
					if ($("searchHighlight").checked) {
						addClass(logMainContainer, "searchhighlight");
					} else {
						removeClass(logMainContainer, "searchhighlight");
					}
					if ($("searchFilter").checked) {
						addClass(logMainContainer, "searchfilter");
					} else {
						removeClass(logMainContainer, "searchfilter");
					}
					$("searchDisable").checked = !enable;
				} else {
					addClass($("search"), "greyedout");
					removeClass(document.body, "searching");
					removeClass(logMainContainer, "searchhighlight");
					removeClass(logMainContainer, "searchfilter");
				}
				setLogContainerHeight();
			}

			function toggleSearchFilter() {
				var enable = $("searchFilter").checked;
				if (enable) {
					addClass(logMainContainer, "searchfilter");
				} else {
					removeClass(logMainContainer, "searchfilter");
				}
				refreshCurrentMatch();
			}

			function toggleSearchHighlight() {
				var enable = $("searchHighlight").checked;
				if (enable) {
					addClass(logMainContainer, "searchhighlight");
				} else {
					removeClass(logMainContainer, "searchhighlight");
				}
			}

			function clearSearch() {
				$("searchBox").value = "";
				doSearch();
			}

			function searchNext() {
				if (currentSearch !== null && currentMatchIndex !== null) {
					currentSearch.matches[currentMatchIndex].setNotCurrent();
					var nextMatchIndex = currentSearch.getNextVisibleMatchIndex();
					if (nextMatchIndex > currentMatchIndex || confirm("Reached the end of the page. Start from the top?")) {
						setCurrentMatchIndex(nextMatchIndex);
					}
				}
			}

			function searchPrevious() {
				if (currentSearch !== null && currentMatchIndex !== null) {
					currentSearch.matches[currentMatchIndex].setNotCurrent();
					var previousMatchIndex = currentSearch.getPreviousVisibleMatchIndex();
					if (previousMatchIndex < currentMatchIndex || confirm("Reached the start of the page. Continue from the bottom?")) {
						setCurrentMatchIndex(previousMatchIndex);
					}
				}
			}

			function setCurrentMatchIndex(index) {
				currentMatchIndex = index;
				currentSearch.matches[currentMatchIndex].setCurrent();
			}

			/* ------------------------------------------------------------------------- */

			// CSS Utilities

			function addClass(el, cssClass) {
				if (!hasClass(el, cssClass)) {
					if (el.className) {
						el.className += " " + cssClass;
					} else {
						el.className = cssClass;
					}
				}
			}

			function hasClass(el, cssClass) {
				if (el.className) {
					var classNames = el.className.split(" ");
					return array_contains(classNames, cssClass);
				}
				return false;
			}

			function removeClass(el, cssClass) {
				if (hasClass(el, cssClass)) {
					// Rebuild the className property
					var existingClasses = el.className.split(" ");
					var newClasses = [];
					for (var i = 0, len = existingClasses.length; i < len; i++) {
						if (existingClasses[i] != cssClass) {
							newClasses[newClasses.length] = existingClasses[i];
						}
					}
					el.className = newClasses.join(" ");
				}
			}

			function replaceClass(el, newCssClass, oldCssClass) {
				removeClass(el, oldCssClass);
				addClass(el, newCssClass);
			}

			/* ------------------------------------------------------------------------- */

			// Other utility functions

			function getElementsByClass(el, cssClass, tagName) {
				var elements = el.getElementsByTagName(tagName);
				var matches = [];
				for (var i = 0, len = elements.length; i < len; i++) {
					if (hasClass(elements[i], cssClass)) {
						matches.push(elements[i]);
					}
				}
				return matches;
			}

			// Syntax borrowed from Prototype library
			function $(id) {
				return document.getElementById(id);
			}

			function isDescendant(node, ancestorNode) {
				while (node != null) {
					if (node === ancestorNode) {
						return true;
					}
					node = node.parentNode;
				}
				return false;
			}

			function isOrphan(node) {
				var currentNode = node;
				while (currentNode) {
					if (currentNode == document.body) {
						return false;
					}
					currentNode = currentNode.parentNode;
				}
				return true;
			}

			function escapeHtml(str) {
				return str.replace(/&/g, "&amp;").replace(/[<]/g, "&lt;").replace(/>/g, "&gt;");
			}

			function getWindowWidth() {
				if (window.innerWidth) {
					return window.innerWidth;
				} else if (document.documentElement && document.documentElement.clientWidth) {
					return document.documentElement.clientWidth;
				} else if (document.body) {
					return document.body.clientWidth;
				}
				return 0;
			}

			function getWindowHeight() {
				if (window.innerHeight) {
					return window.innerHeight;
				} else if (document.documentElement && document.documentElement.clientHeight) {
					return document.documentElement.clientHeight;
				} else if (document.body) {
					return document.body.clientHeight;
				}
				return 0;
			}

			function getToolBarsHeight() {
				return $("switches").offsetHeight;
			}

			function getChromeHeight() {
				var height = getToolBarsHeight();
				if (showCommandLine) {
					height += $("commandLine").offsetHeight;
				}
				return height;
			}

			function setLogContainerHeight() {
				if (logMainContainer) {
					var windowHeight = getWindowHeight();
					$("body").style.height = getWindowHeight() + "px";
					logMainContainer.style.height = "" +
						Math.max(0, windowHeight - getChromeHeight()) + "px";
				}
			}

			function setCommandInputWidth() {
				if (showCommandLine) {
					$("command").style.width = "" + Math.max(0, $("commandLineContainer").offsetWidth -
						($("evaluateButton").offsetWidth + 13)) + "px";
				}
			}

			window.onresize = function() {
				setCommandInputWidth();
				setLogContainerHeight();
			};

			if (!Array.prototype.push) {
				Array.prototype.push = function() {
			        for (var i = 0, len = arguments.length; i < len; i++){
			            this[this.length] = arguments[i];
			        }
			        return this.length;
				};
			}

			if (!Array.prototype.pop) {
				Array.prototype.pop = function() {
					if (this.length > 0) {
						var val = this[this.length - 1];
						this.length = this.length - 1;
						return val;
					}
				};
			}

			if (!Array.prototype.shift) {
				Array.prototype.shift = function() {
					if (this.length > 0) {
						var firstItem = this[0];
						for (var i = 0, len = this.length - 1; i < len; i++) {
							this[i] = this[i + 1];
						}
						this.length = this.length - 1;
						return firstItem;
					}
				};
			}

			if (!Array.prototype.splice) {
				Array.prototype.splice = function(startIndex, deleteCount) {
					var itemsAfterDeleted = this.slice(startIndex + deleteCount);
					var itemsDeleted = this.slice(startIndex, startIndex + deleteCount);
					this.length = startIndex;
					// Copy the arguments into a proper Array object
					var argumentsArray = [];
					for (var i = 0, len = arguments.length; i < len; i++) {
						argumentsArray[i] = arguments[i];
					}
					var itemsToAppend = (argumentsArray.length > 2) ?
						itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted;
					for (i = 0, len = itemsToAppend.length; i < len; i++) {
						this.push(itemsToAppend[i]);
					}
					return itemsDeleted;
				};
			}

			function array_remove(arr, val) {
				var index = -1;
				for (var i = 0, len = arr.length; i < len; i++) {
					if (arr[i] === val) {
						index = i;
						break;
					}
				}
				if (index >= 0) {
					arr.splice(index, 1);
					return index;
				} else {
					return false;
				}
			}

			function array_removeFromStart(array, numberToRemove) {
				if (Array.prototype.splice) {
					array.splice(0, numberToRemove);
				} else {
					for (var i = numberToRemove, len = array.length; i < len; i++) {
						array[i - numberToRemove] = array[i];
					}
					array.length = array.length - numberToRemove;
				}
				return array;
			}

			function array_contains(arr, val) {
				for (var i = 0, len = arr.length; i < len; i++) {
					if (arr[i] == val) {
						return true;
					}
				}
				return false;
			}

			function getErrorMessage(ex) {
				if (ex.message) {
					return ex.message;
				} else if (ex.description) {
					return ex.description;
				}
				return "" + ex;
			}

			function moveCaretToEnd(input) {
				if (input.setSelectionRange) {
					input.focus();
					var length = input.value.length;
					input.setSelectionRange(length, length);
				} else if (input.createTextRange) {
					var range = input.createTextRange();
					range.collapse(false);
					range.select();
				}
				input.focus();
			}

			function stopPropagation(evt) {
				if (evt.stopPropagation) {
					evt.stopPropagation();
				} else if (typeof evt.cancelBubble != "undefined") {
					evt.cancelBubble = true;
				}
			}

			function getEvent(evt) {
				return evt ? evt : event;
			}

			function getTarget(evt) {
				return evt.target ? evt.target : evt.srcElement;
			}

			function getRelatedTarget(evt) {
				if (evt.relatedTarget) {
					return evt.relatedTarget;
				} else if (evt.srcElement) {
					switch(evt.type) {
						case "mouseover":
							return evt.fromElement;
						case "mouseout":
							return evt.toElement;
						default:
							return evt.srcElement;
					}
				}
			}

			function cancelKeyEvent(evt) {
				evt.returnValue = false;
				stopPropagation(evt);
			}

			function evalCommandLine() {
				var expr = $("command").value;
				evalCommand(expr);
				$("command").value = "";
			}

			function evalLastCommand() {
				if (lastCommand != null) {
					evalCommand(lastCommand);
				}
			}

			var lastCommand = null;
			var commandHistory = [];
			var currentCommandIndex = 0;

			function evalCommand(expr) {
				if (appender) {
					appender.evalCommandAndAppend(expr);
				} else {
					var prefix = ">>> " + expr + "\r\n";
					try {
						log("INFO", prefix + eval(expr));
					} catch (ex) {
						log("ERROR", prefix + "Error: " + getErrorMessage(ex));
					}
				}
				// Update command history
				if (expr != commandHistory[commandHistory.length - 1]) {
					commandHistory.push(expr);
					// Update the appender
					if (appender) {
						appender.storeCommandHistory(commandHistory);
					}
				}
				currentCommandIndex = (expr == commandHistory[currentCommandIndex]) ? currentCommandIndex + 1 : commandHistory.length;
				lastCommand = expr;
			}
			//]]>
		</script>
		<style type="text/css">
			body {
				background-color: white;
				color: black;
				padding: 0;
				margin: 0;
				font-family: tahoma, verdana, arial, helvetica, sans-serif;
				overflow: hidden;
			}

			div#switchesContainer input {
				margin-bottom: 0;
			}

			div.toolbar {
				border-top: solid #ffffff 1px;
				border-bottom: solid #aca899 1px;
				background-color: #f1efe7;
				padding: 3px 5px;
				font-size: 68.75%;
			}

			div.toolbar, div#search input {
				font-family: tahoma, verdana, arial, helvetica, sans-serif;
			}

			div.toolbar input.button {
				padding: 0 5px;
				font-size: 100%;
			}

			div.toolbar input.hidden {
				display: none;
			}

			div#switches input#clearButton {
				margin-left: 20px;
			}

			div#levels label {
				font-weight: bold;
			}

			div#levels label, div#options label {
				margin-right: 5px;
			}

			div#levels label#wrapLabel {
				font-weight: normal;
			}

			div#search label {
				margin-right: 10px;
			}

			div#search label.searchboxlabel {
				margin-right: 0;
			}

			div#search input {
				font-size: 100%;
			}

			div#search input.validregex {
				color: green;
			}

			div#search input.invalidregex {
				color: red;
			}

			div#search input.nomatches {
				color: white;
				background-color: #ff6666;
			}

			div#search input.nomatches {
				color: white;
				background-color: #ff6666;
			}

			div#searchNav {
				display: none;
			}

			div#commandLine {
				display: none;
			}

			div#commandLine input#command {
				font-size: 100%;
				font-family: Courier New, Courier;
			}

			div#commandLine input#evaluateButton {
			}

			*.greyedout {
				color: gray !important;
				border-color: gray !important;
			}

			*.greyedout *.alwaysenabled { color: black; }

			*.unselectable {
				-khtml-user-select: none;
				-moz-user-select: none;
				user-select: none;
			}

			div#log {
				font-family: Courier New, Courier;
				font-size: 75%;
				width: 100%;
				overflow: auto;
				clear: both;
				position: relative;
			}

			div.group {
				border-color: #cccccc;
				border-style: solid;
				border-width: 1px 0 1px 1px;
				overflow: visible;
			}

			div.oldIe div.group, div.oldIe div.group *, div.oldIe *.logentry {
				height: 1%;
			}

			div.group div.groupheading span.expander {
				border: solid black 1px;
				font-family: Courier New, Courier;
				font-size: 0.833em;
				background-color: #eeeeee;
				position: relative;
				top: -1px;
				color: black;
				padding: 0 2px;
				cursor: pointer;
				cursor: hand;
				height: 1%;
			}

			div.group div.groupcontent {
				margin-left: 10px;
				padding-bottom: 2px;
				overflow: visible;
			}

			div.group div.expanded {
				display: block;
			}

			div.group div.collapsed {
				display: none;
			}

			*.logentry {
				overflow: visible;
				display: none;
				white-space: pre;
			}

			span.pre {
				white-space: pre;
			}
			
			pre.unwrapped {
				display: inline !important;
			}

			pre.unwrapped pre.pre, div.wrapped pre.pre {
				display: inline;
			}

			div.wrapped pre.pre {
				white-space: normal;
			}

			div.wrapped {
				display: none;
			}

			body.searching *.logentry span.currentmatch {
				color: white !important;
				background-color: green !important;
			}

			body.searching div.searchhighlight *.logentry span.searchterm {
				color: black;
				background-color: yellow;
			}

			div.wrap *.logentry {
				white-space: normal !important;
				border-width: 0 0 1px 0;
				border-color: #dddddd;
				border-style: dotted;
			}

			div.wrap #log_wrapped, #log_unwrapped {
				display: block;
			}

			div.wrap #log_unwrapped, #log_wrapped {
				display: none;
			}

			div.wrap *.logentry span.pre {
				overflow: visible;
				white-space: normal;
			}

			div.wrap *.logentry pre.unwrapped {
				display: none;
			}

			div.wrap *.logentry span.wrapped {
				display: inline;
			}

			div.searchfilter *.searchnonmatch {
				display: none !important;
			}

			div#log *.TRACE, label#label_TRACE {
				color: #666666;
			}

			div#log *.DEBUG, label#label_DEBUG {
				color: green;
			}

			div#log *.INFO, label#label_INFO {
				color: #000099;
			}

			div#log *.WARN, label#label_WARN {
				color: #999900;
			}

			div#log *.ERROR, label#label_ERROR {
				color: red;
			}

			div#log *.FATAL, label#label_FATAL {
				color: #660066;
			}

			div.TRACE#log *.TRACE,
			div.DEBUG#log *.DEBUG,
			div.INFO#log *.INFO,
			div.WARN#log *.WARN,
			div.ERROR#log *.ERROR,
			div.FATAL#log *.FATAL {
				display: block;
			}

			div#log div.separator {
				background-color: #cccccc;
				margin: 5px 0;
				line-height: 1px;
			}
		</style>
	</head>

	<body id="body">
		<div id="switchesContainer">
			<div id="switches">
				<div id="levels" class="toolbar">
					Filters:
					<input type="checkbox" id="switch_TRACE" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide trace messages" /><label for="switch_TRACE" id="label_TRACE">trace</label>
					<input type="checkbox" id="switch_DEBUG" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide debug messages" /><label for="switch_DEBUG" id="label_DEBUG">debug</label>
					<input type="checkbox" id="switch_INFO" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide info messages" /><label for="switch_INFO" id="label_INFO">info</label>
					<input type="checkbox" id="switch_WARN" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide warn messages" /><label for="switch_WARN" id="label_WARN">warn</label>
					<input type="checkbox" id="switch_ERROR" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide error messages" /><label for="switch_ERROR" id="label_ERROR">error</label>
					<input type="checkbox" id="switch_FATAL" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide fatal messages" /><label for="switch_FATAL" id="label_FATAL">fatal</label>
					<input type="checkbox" id="switch_ALL" onclick="toggleAllLevels(); applyFilters()" checked="checked" title="Show/hide all messages" /><label for="switch_ALL" id="label_ALL">all</label>
				</div>
				<div id="search" class="toolbar">
					<label for="searchBox" class="searchboxlabel">Search:</label> <input type="text" id="searchBox" onclick="toggleSearchEnabled(true)" onkeyup="scheduleSearch()" size="20" />
					<input type="button" id="searchReset" disabled="disabled" value="Reset" onclick="clearSearch()" class="button" title="Reset the search" />
					<input type="checkbox" id="searchRegex" onclick="doSearch()" title="If checked, search is treated as a regular expression" /><label for="searchRegex">Regex</label>
					<input type="checkbox" id="searchCaseSensitive" onclick="doSearch()" title="If checked, search is case sensitive" /><label for="searchCaseSensitive">Match case</label>
					<input type="checkbox" id="searchDisable" onclick="toggleSearchEnabled()" title="Enable/disable search" /><label for="searchDisable" class="alwaysenabled">Disable</label>
					<div id="searchNav">
						<input type="button" id="searchNext" disabled="disabled" value="Next" onclick="searchNext()" class="button" title="Go to the next matching log entry" />
						<input type="button" id="searchPrevious" disabled="disabled" value="Previous" onclick="searchPrevious()" class="button" title="Go to the previous matching log entry" />
						<input type="checkbox" id="searchFilter" onclick="toggleSearchFilter()" title="If checked, non-matching log entries are filtered out" /><label for="searchFilter">Filter</label>
						<input type="checkbox" id="searchHighlight" onclick="toggleSearchHighlight()" title="Highlight matched search terms" /><label for="searchHighlight" class="alwaysenabled">Highlight all</label>
					</div>
				</div>
				<div id="options" class="toolbar">
					Options:
					<input type="checkbox" id="enableLogging" onclick="toggleLoggingEnabled()" checked="checked" title="Enable/disable logging" /><label for="enableLogging" id="enableLoggingLabel">Log</label>
					<input type="checkbox" id="wrap" onclick="toggleWrap()" title="Enable / disable word wrap" /><label for="wrap" id="wrapLabel">Wrap</label>
					<input type="checkbox" id="newestAtTop" onclick="toggleNewestAtTop()" title="If checked, causes newest messages to appear at the top" /><label for="newestAtTop" id="newestAtTopLabel">Newest at the top</label>
					<input type="checkbox" id="scrollToLatest" onclick="toggleScrollToLatest()" checked="checked" title="If checked, window automatically scrolls to a new message when it is added" /><label for="scrollToLatest" id="scrollToLatestLabel">Scroll to latest</label>
					<input type="button" id="clearButton" value="Clear" onclick="clearLog()" class="button" title="Clear all log messages"  />
					<input type="button" id="hideButton" value="Hide" onclick="hide()" class="hidden button" title="Hide the console" />
					<input type="button" id="closeButton" value="Close" onclick="closeWindow()" class="hidden button" title="Close the window" />
				</div>
			</div>
		</div>
		<div id="log" class="TRACE DEBUG INFO WARN ERROR FATAL"></div>
		<div id="commandLine" class="toolbar">
			<div id="commandLineContainer">
				<input type="text" id="command" title="Enter a JavaScript command here and hit return or press 'Evaluate'" />
				<input type="button" id="evaluateButton" value="Evaluate" class="button" title="Evaluate the command" onclick="evalCommandLine()" />
			</div>
		</div>
	</body>
</html>
